﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Arborium
{
    public partial class AElement
    {
        #region Properties

        /// <summary>
        /// Data storage
        /// </summary>
        public Stream Data { get; set; }

        /// <summary>
        /// Metadata storage
        /// </summary>
        public Stream MetaData { get; set; }

        /// <summary>
        /// Reference to the parent node; root node has null as parent.
        /// </summary>
        public AElement Parent { get; set; }

        internal List<AElement> _children;

        /// <summary>
        /// List of children. it is never null.
        /// </summary>
        public IQueryable<AElement> Children
        {
            get
            {
                return _children.AsQueryable();
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Conctructor without any data
        /// </summary>
        public AElement()
        {
            _children = new List<AElement>();
        }

        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="other">Name of the object to by copied</param>
        public AElement(AElement other)
        {
            Data = other.Data;
            MetaData = other.MetaData;

            Parent = other.Parent;
            _children = new List<AElement>(other.Children.Where(child => child != null));

            foreach (AElement child in _children)
            {
                child.Parent = this;
            }
        }

        /// <summary>
        /// Contructor that also adds child elements
        /// </summary>
        /// <param name="data">Data of the parent node</param>
        /// <param name="metaData"></param>
        /// <param name="children">List of child nodes</param>
        public AElement(Stream data, Stream metaData, params AElement[] children)
        {
            Data = data;
            MetaData = metaData;

            if (children != null)
            {
                _children = new List<AElement>(children.Where(child => child != null));

                foreach (AElement child in _children)
                {
                    child.Parent = this;
                }
            }
            else
            {
                _children = new List<AElement>();
            }
        }

        /// <summary>
        /// LINQ friendly Contructor that also adds child elements
        /// </summary>
        /// <param name="data">Data of the parent node</param>
        /// <param name="metaData"></param>
        /// <param name="children">LINQ query of child nodes</param>
        public AElement(Stream data, Stream metaData, IEnumerable<AElement> children)
        {
            Data = data;
            MetaData = metaData;

            if (children != null)
            {
                _children = new List<AElement>(children.Where(child => child != null));

                foreach (AElement child in _children)
                {
                    child.Parent = this;
                }
            }
            else
            {
                _children = new List<AElement>();
            }
        }

        #endregion

        #region Indexer

        public AElement this[int index]
        {
            get
            {
                if (index >= 0 && index < _children.Count)
                {
                    return _children[index];
                }

                throw new AException(String.Format("Index {0} in out of range [0...{1}]", index, _children.Count));
            }
        }

        #endregion

        #region Tree operations

        /// <summary>
        /// Add node(s) to the tree. Node can be null, in this case nothing will happen.
        /// </summary>
        /// <param name="nodes">The nodes to be added</param>
        public void Add(params AElement[] nodes)
        {
            if (nodes != null)
            {
                foreach (AElement node in nodes)
                {
                    if (node != null)
                    {
                        if (!Children.Contains(node))
                        {
                            UnsafeAdd(node);
                        }
                        else
                        {
                            throw new AException("Node already exists in the tree");
                        }
                    }
                }
            }
        }


        /// <summary>
        /// Add node(s) to the tree. Node can be null, in this case nothing will happen.
        /// </summary>
        /// <param name="nodes">The nodes to be added</param>
        public void Add(IEnumerable<AElement> nodes)
        {
            if (nodes != null)
            {
                foreach (AElement node in nodes)
                {
                    Add(node);
                }
            }
        }

        /// <summary>
        /// Add node(s) to the tree. Node can be null, in this case nothing will happen.
        /// </summary>
        /// <param name="tree">The tree to be extended</param>
        /// <param name="node">The node to be added</param>
        public static AElement operator +(AElement tree, AElement node)
        {
            tree.Add(node);
            return tree;
        }

        /// <summary>
        /// Removes a subtree or node recursively.
        /// </summary>
        /// <param name="node">The node to be removed</param>
        public void Remove(AElement node)
        {
            if (node != null)
            {
                if (node.Children == null)
                {
                    throw new AException("Children list must not be null");
                }

                List<AElement> elementsToRemove = new List<AElement>(node.Children);

                foreach (AElement child in elementsToRemove)
                {
                    Remove(child);
                }

                Detach(node);
            }
        }

        /// <summary>
        /// Removes a subtree or node recursively.
        /// </summary>
        /// <param name="tree">The tree to be shrinked</param>
        /// <param name="node">The node to be removed</param>
        public static AElement operator -(AElement tree, AElement node)
        {
            tree.Remove(node);
            return tree;
        }

        /// <summary>
        /// Detaches a subtree or node.
        /// </summary>
        /// <param name="node">The node to be detached</param>
        /// <returns>The node itself</returns>
        public AElement Detach(AElement node)
        {
            if (node != null && node.Parent != null)
            {
                if (node.Parent.Children.Contains(node))
                {
                    node.Parent._children.Remove(node);
                }
                else
                {
                    throw new AException("Node can not be detached from its parent");
                }

                node.Parent = null;
            }

            return node;
        }

        /// <summary>
        /// The method is called for each nodes.
        /// </summary>
        /// <param name="node">The current node</param>
        /// <param name="ancestors">A list of ancestors</param>
        public delegate void WalkHandler(AElement node, List<AElement> ancestors);

        /// <summary>
        /// Walks through the tree, and calls the handler for each node.
        /// </summary>
        /// <param name="handler">The method to be called for each nodes</param>
        public void Walk(WalkHandler handler)
        {
            List<AElement> ancestors = new List<AElement>();

            if (handler != null)
            {
                handler(this, ancestors);
            }

            Walk(this, ancestors, handler);
        }

        #endregion

        #region Conversion functions

        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();

            NodeToString(this, 0, stringBuilder, false);

            return stringBuilder.ToString().Trim();
        }

        public string TreeToString()
        {
            StringBuilder stringBuilder = new StringBuilder();

            NodeToString(this, 0, stringBuilder, true);

            return stringBuilder.ToString();
        }

        public string ToBase64String()
        {
            string base64String;

            using (MemoryStream memoryStream = new MemoryStream())
            {
                Save(memoryStream);

                byte[] buffer = new byte[memoryStream.Length];
                Array.Copy(memoryStream.AsByteArray(), buffer, (int)memoryStream.Length);

                base64String = Convert.ToBase64String(buffer);
            }

            return base64String;
        }

        public static AElement FromBase64String(string base64String)
        {
            AElement tree;

            using (MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(base64String)))
            {
                tree = Load(memoryStream);
            }

            return tree;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Add node(s) to the tree, unsafely, but quickly. Node can be null, in this case nothing will happen.
        /// </summary>
        /// <param name="node">The node to be added</param>
        private void UnsafeAdd(AElement node)
        {
            _children.Add(node);
            node.Parent = this;
        }

        private void NodeToString(AElement node, int level, StringBuilder stringBuilder, bool recursive)
        {
            if (recursive)
            {
                for (int i = 0; i < level; ++i)
                {
                    stringBuilder.Append("> ");
                }
            }

            stringBuilder.Append("(");

            if (Contains(node.MetaData))
            {
                stringBuilder.Append("[");
                StreamToString(node.MetaData, stringBuilder);
                stringBuilder.Append("]");
            }

            if (Contains(node.Data) && Contains(node.MetaData))
            {
                stringBuilder.Append(" ");
            }

            if (Contains(node.Data))
            {
                StreamToString(node.Data, stringBuilder);
            }

            stringBuilder.Append(")\n");

            if (recursive)
            {
                foreach (AElement child in node.Children)
                {
                    NodeToString(child, level + 1, stringBuilder, true);
                }
            }
        }

        private void StreamToString(Stream data, StringBuilder stringBuilder)
        {
            data.Position = 0;

            while (data.Position < data.Length)
            {
                stringBuilder.AppendFormat("{0:X2}", data.ReadByte());
            }
        }

        private void Walk(AElement parent, List<AElement> ancestors, WalkHandler handler)
        {
            if (parent._children != null)
            {
                List<AElement> _ancestors = new List<AElement>(ancestors);
                _ancestors.Add(parent);

                foreach (AElement child in parent._children)
                {
                    if (handler != null)
                    {
                        handler(child, _ancestors);
                    }

                    Walk(child, _ancestors, handler);
                }
            }
        }

        #endregion
    }
}
